﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Collections;

namespace Logic
{
    public static class DecryptionRoutine
    {
        [DllImport("msvcrt.dll")]
        public static extern int srand(int seed);

        [DllImport("msvcrt.dll")]
        public static extern int rand();


        [DllImport("msvcrt.dll")]
        public static extern long lrand();

        public class TCryptoTables
        {
            public int[] xor;
            public int[] offsets;
            public uint[] algo;

            public TCryptoTables(int length)
            {
                xor = new int[length];
                offsets = new int[length];
                algo = new uint[length];
            }

        }


        public static byte[] Xor(byte[] data)
        {

            for (int i = 0; i < data.Length; i++)
            {
                // 99 hex = -103int
                data[i] ^= BitConverter.GetBytes(-103)[0];
            }

            return data;
        }
        public static byte[] Pak_SwitchEncryptionOff_150(byte[] pak, byte[] seed)
        {
            // the 1.50 protocol cryptography uses extensively the standard C random number generator,
            // which is a VERY BAD idea, since its implementation may differ from system to system !!!
            unsafe
            {
                TCryptoTables cryptotables_150 = new TCryptoTables(pak.Length);
                sbyte[] stack1 = new sbyte[10];
                sbyte[] stack2 = new sbyte[10];
                byte a;
                byte c;
                byte edi;
                byte ebp;
                int index;
                uint algo;
                short checksum;
                byte XorTemp, XorTemp2;

                // initialize the system's pseudo-random number generator from the seed given in the datagram
                // (they apparently swapped the bytes in an attempt to confuse the reverse-engineerers)
                srand((int)((seed)[0] << 24)
                       | (int)((seed)[3] << 16)
                       | (int)((seed)[1] << 8)
                       | (int)((seed)[2]));

                // now generate the crypto tables for the given datagram length

                // stack sequences
                for (index = 0; index < 10; index++)
                {
                    stack1[index] = (sbyte)rand();
                    stack2[index] = (sbyte)rand();
                }

                for (int i = 0; i < pak.Length; i++)
                {
                    XorTemp = (byte)stack2[rand() % 10];
                    XorTemp2 = (byte)stack1[rand() % 10];
                    cryptotables_150.xor[i] = XorTemp * XorTemp2;
                    cryptotables_150.xor[i] = cryptotables_150.xor[i] + rand();
                }

                // offset & algo tables
                for (index = 0; index < pak.Length; index++)
                {
                    cryptotables_150.offsets[index] = rand() % pak.Length;
                    if (cryptotables_150.offsets[index] == (uint)index)
                        cryptotables_150.offsets[index] = (index == 0 ? 1 : 0);

                    cryptotables_150.algo[index] = (uint)rand() % 21;
                }

                // cryptographic tables are generated, now apply the algorithm
                for (index = pak.Length - 1; index >= 0; index--)
                {
                    algo = cryptotables_150.algo[index];
                    ebp = pak[cryptotables_150.offsets[index]];
                    edi = pak[index];

                    a = ebp;
                    c = edi;

                    if (algo == 0) { pak[index] = (byte)(((a ^ c) & 0x0F) ^ c); pak[cryptotables_150.offsets[index]] = (byte)(((a ^ c) & 0x0F) ^ a); }
                    else if (algo == 1) { pak[index] = (byte)(((a ^ c) & 0x0F) ^ c); pak[cryptotables_150.offsets[index]] = (byte)((a >> 4) | (c << 4)); }
                    else if (algo == 2) { pak[index] = (byte)((c >> 4) | (c << 4)); pak[cryptotables_150.offsets[index]] = (byte)((a >> 4) | (a << 4)); }
                    else if (algo == 3) { pak[index] = (byte)((a >> 4) | (c << 4)); pak[cryptotables_150.offsets[index]] = (byte)(((a ^ c) & 0x0F) ^ c); }
                    else if (algo == 4) { pak[index] = (byte)((a & 0x0F) | (c << 4)); pak[cryptotables_150.offsets[index]] = (byte)((a & 0xF0) | (c >> 4)); }
                    else if (algo == 5) { pak[index] = (byte)((c & 0xF0) | (a >> 4)); pak[cryptotables_150.offsets[index]] = (byte)((a << 4) | (c & 0x0F)); }
                    else if (algo == 6) { pak[index] = (byte)((a >> 4) | (c << 4)); pak[cryptotables_150.offsets[index]] = (byte)((a << 4) | (c >> 4)); }
                    else if (algo == 7) { pak[index] = (byte)((c & 0xF0) | (a >> 4)); pak[cryptotables_150.offsets[index]] = (byte)((a & 0x0F) | (c << 4)); }
                    else if (algo == 8) { pak[index] = (byte)((a & 0x0F) | (c << 4)); pak[cryptotables_150.offsets[index]] = (byte)((c & 0xF0) | (a >> 4)); }
                    else if (algo == 9) { pak[index] = (byte)((a & 0xF0) | (c >> 4)); pak[cryptotables_150.offsets[index]] = (byte)((a & 0x0F) | (c << 4)); }
                    else if (algo == 10) { pak[index] = (byte)((a << 4) | (c & 0x0F)); pak[cryptotables_150.offsets[index]] = (byte)((a & 0xF0) | (c >> 4)); }
                    else if (algo == 11) { pak[index] = (byte)((a << 4) | (c >> 4)); pak[cryptotables_150.offsets[index]] = (byte)(((a ^ c) & 0x0F) ^ a); }
                    else if (algo == 12) { pak[index] = (byte)((a >> 4) | (a << 4)); pak[cryptotables_150.offsets[index]] = (byte)((c >> 4) | (c << 4)); }
                    else if (algo == 13) { pak[index] = (byte)a; pak[cryptotables_150.offsets[index]] = c; }
                    else if (algo == 14) { pak[index] = (byte)((a & 0xF0) | (c >> 4)); pak[cryptotables_150.offsets[index]] = (byte)((a << 4) | (c & 0x0F)); }
                    else if (algo == 15) { pak[index] = (byte)(((a ^ c) & 0x0F) ^ a); pak[cryptotables_150.offsets[index]] = (byte)(((a ^ c) & 0x0F) ^ c); }
                    else if (algo == 16) { pak[index] = (byte)a; ebp = (byte)((c >> 4) | (c << 4)); }
                    else if (algo == 17) { pak[index] = (byte)((a << 4) | (c & 0x0F)); pak[cryptotables_150.offsets[index]] = (byte)((c & 0xF0) | (a >> 4)); }
                    else if (algo == 18) { pak[index] = (byte)((a << 4) | (c >> 4)); pak[cryptotables_150.offsets[index]] = (byte)((a >> 4) | (c << 4)); }
                    else if (algo == 19) { pak[index] = (byte)((a >> 4) | (a << 4)); pak[cryptotables_150.offsets[index]] = c; }
                    else if (algo == 20) { pak[index] = (byte)(((a ^ c) & 0x0F) ^ a); pak[cryptotables_150.offsets[index]] = (byte)((a << 4) | (c >> 4)); }
                }

                // and finally, quadruple-XOR the data out
                for (index = pak.Length - 1; index >= 0; index--)
                {
                    if (index <= pak.Length - 4)
                    {
                        pak[index + 0] ^= (byte)(cryptotables_150.xor[index] & 0x000000FF); // we can XOR 4 bytes in a row
                        pak[index + 1] ^= (byte)((cryptotables_150.xor[index] & 0x0000FF00) >> 8);
                        pak[index + 2] ^= (byte)((cryptotables_150.xor[index] & 0x00FF0000) >> 16);
                        pak[index + 3] ^= (byte)((cryptotables_150.xor[index] & 0xFF000000) >> 24);
                    }
                    else if (index == pak.Length - 3)
                    {
                        pak[index + 0] ^= (byte)(cryptotables_150.xor[index] & 0x0000FF); // we can XOR 3 bytes in a row
                        pak[index + 1] ^= (byte)((cryptotables_150.xor[index] & 0x00FF00) >> 8);
                        pak[index + 2] ^= (byte)((cryptotables_150.xor[index] & 0xFF0000) >> 16);
                    }
                    else if (index == pak.Length - 2)
                    {
                        pak[index + 0] ^= (byte)(cryptotables_150.xor[index] & 0x00FF); // we can XOR 2 bytes in a row
                        pak[index + 1] ^= (byte)((cryptotables_150.xor[index] & 0xFF00) >> 8);
                    }
                    else if (index == pak.Length - 1)
                        pak[index] ^= (byte)(cryptotables_150.xor[index] & 0xFF); // end of stream
                }

                // in the 1.50 protocol, the checksum info is at the trailing end of the pak.
                checksum = (short)pak[pak.Length - 2]; // so get it from there...
                // pak.Length -= 2; // ...and correct the data size
                SoapHexBinary shb = new SoapHexBinary(pak);
                string hexa;
                //hexa = shb.ToString();
                return (pak); // finished, pak is decrypted
            }
        }

        public static short Pak_SwitchEncryptionOn_150(byte[] pak)
        {
            // the 1.50 protocol cryptography uses extensively the standard C random number generator,
            // which is a VERY BAD idea, since its implementation may differ from system to system !!!
            unsafe
            {
                byte[] reverse_tree = new byte[21] { 0, 5, 2, 9, 11, 1, 18, 7, 14, 3, 10, 4, 12, 13, 8, 15, 19, 20, 6, 16, 17 };
                //byte[] seed = SoapHexBinary.Parse("29a0d04d").Value;
                // byte[] seed;
                byte[] return_seed = new byte[4];
                TCryptoTables cryptotables_150 = new TCryptoTables(pak.Length);
                sbyte[] stack1 = new sbyte[10];
                sbyte[] stack2 = new sbyte[10];
                byte a;
                byte c;
                byte edi;
                byte ebp;
                int index;
                uint algo;
                short checksum;
                byte XorTemp, XorTemp2;


                //long seedtime = (long)DateTime.Now.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
                long seed = 725472321 * (long)DateTime.Now.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 1;

                //  long d = lrand();
                // seed = System.BitConverter.GetBytes(temprand);
                srand((int)seed);


                // seed = BitConverter.GetBytes(seed_int);

                // initialize the system's pseudo-random number generator from the seed given in the datagram
                // (they apparently swapped the bytes in an attempt to confuse the reverse-engineerers)

                return_seed[0] = (byte)(seed >> 24);
                return_seed[3] = (byte)(seed >> 16);
                return_seed[1] = (byte)(seed >> 8);
                return_seed[2] = (byte)(seed);

                // now generate the crypto tables for the given datagram length

                // stack sequences
                for (index = 0; index < 10; index++)
                {
                    stack1[index] = (sbyte)rand();
                    stack2[index] = (sbyte)rand();
                }

                // xor table
                for (int i = 0; i < pak.Length; i++)
                {
                    XorTemp = (byte)stack2[rand() % 10];
                    XorTemp2 = (byte)stack1[rand() % 10];
                    cryptotables_150.xor[i] = XorTemp * XorTemp2;
                    cryptotables_150.xor[i] = cryptotables_150.xor[i] + rand();
                }

                // offset & algo tables
                for (index = 0; index < pak.Length; index++)
                {
                    cryptotables_150.offsets[index] = rand() % pak.Length;
                    if (cryptotables_150.offsets[index] == (uint)index)
                        cryptotables_150.offsets[index] = (index == 0 ? 1 : 0);

                    cryptotables_150.algo[index] = (uint)rand() % 21;
                }

                // cryptographic tables are generated, now quadruple-XOR the data out
                for (index = pak.Length - 1; index >= 0; index--)
                {
                    if (index <= pak.Length - 4)
                    {
                        pak[index + 0] ^= (byte)(cryptotables_150.xor[index] & 0x000000FF); // we can XOR 4 bytes in a row
                        pak[index + 1] ^= (byte)((cryptotables_150.xor[index] & 0x0000FF00) >> 8);
                        pak[index + 2] ^= (byte)((cryptotables_150.xor[index] & 0x00FF0000) >> 16);
                        pak[index + 3] ^= (byte)((cryptotables_150.xor[index] & 0xFF000000) >> 24);
                    }
                    else if (index == pak.Length - 3)
                    {
                        pak[index + 0] ^= (byte)(cryptotables_150.xor[index] & 0x0000FF); // we can XOR 3 bytes in a row
                        pak[index + 1] ^= (byte)((cryptotables_150.xor[index] & 0x00FF00) >> 8);
                        pak[index + 2] ^= (byte)((cryptotables_150.xor[index] & 0xFF0000) >> 16);
                    }
                    else if (index == pak.Length - 2)
                    {
                        pak[index + 0] ^= (byte)(cryptotables_150.xor[index] & 0x00FF); // we can XOR 2 bytes in a row
                        pak[index + 1] ^= (byte)((cryptotables_150.xor[index] & 0xFF00) >> 8);
                    }
                    else if (index == pak.Length - 1)
                        pak[index] ^= (byte)(cryptotables_150.xor[index] & 0xFF); // end of stream
                }

                // // and finally, apply the algorithm
                for (index = pak.Length - 1; index >= 0; index--)
                {
                    // actually, reverse_tree is a switch table hardcoded by cl compiler (MSVC)
                    algo = reverse_tree[cryptotables_150.algo[pak.Length - 1 - index]];
                    ebp = pak[cryptotables_150.offsets[pak.Length - 1 - index]];
                    edi = pak[pak.Length - 1 - index];

                    a = ebp;
                    c = edi;

                    if (algo == 0) { pak[pak.Length - 1 - index] = (byte)(((a ^ c) & 0x0F) ^ c); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)(((a ^ c) & 0x0F) ^ a); }
                    else if (algo == 1) { pak[pak.Length - 1 - index] = (byte)(((a ^ c) & 0x0F) ^ c); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a >> 4) | (c << 4)); }
                    else if (algo == 2) { pak[pak.Length - 1 - index] = (byte)((c >> 4) | (c << 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a >> 4) | (a << 4)); }
                    else if (algo == 3) { pak[pak.Length - 1 - index] = (byte)((a >> 4) | (c << 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)(((a ^ c) & 0x0F) ^ c); }
                    else if (algo == 4) { pak[pak.Length - 1 - index] = (byte)((a & 0x0F) | (c << 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a & 0xF0) | (c >> 4)); }
                    else if (algo == 5) { pak[pak.Length - 1 - index] = (byte)((c & 0xF0) | (a >> 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a << 4) | (c & 0x0F)); }
                    else if (algo == 6) { pak[pak.Length - 1 - index] = (byte)((a >> 4) | (c << 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a << 4) | (c >> 4)); }
                    else if (algo == 7) { pak[pak.Length - 1 - index] = (byte)((c & 0xF0) | (a >> 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a & 0x0F) | (c << 4)); }
                    else if (algo == 8) { pak[pak.Length - 1 - index] = (byte)((a & 0x0F) | (c << 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((c & 0xF0) | (a >> 4)); }
                    else if (algo == 9) { pak[pak.Length - 1 - index] = (byte)((a & 0xF0) | (c >> 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a & 0x0F) | (c << 4)); }
                    else if (algo == 10) { pak[pak.Length - 1 - index] = (byte)((a << 4) | (c & 0x0F)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a & 0xF0) | (c >> 4)); }
                    else if (algo == 11) { pak[pak.Length - 1 - index] = (byte)((a << 4) | (c >> 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)(((a ^ c) & 0x0F) ^ a); }
                    else if (algo == 12) { pak[pak.Length - 1 - index] = (byte)((a >> 4) | (a << 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((c >> 4) | (c << 4)); }
                    else if (algo == 13) { pak[pak.Length - 1 - index] = (byte)a; pak[cryptotables_150.offsets[pak.Length - 1 - index]] = c; }
                    else if (algo == 14) { pak[pak.Length - 1 - index] = (byte)((a & 0xF0) | (c >> 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a << 4) | (c & 0x0F)); }
                    else if (algo == 15) { pak[pak.Length - 1 - index] = (byte)(((a ^ c) & 0x0F) ^ a); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)(((a ^ c) & 0x0F) ^ c); }
                    else if (algo == 16) { pak[pak.Length - 1 - index] = (byte)a; ebp = (byte)((c >> 4) | (c << 4)); }
                    else if (algo == 17) { pak[pak.Length - 1 - index] = (byte)((a << 4) | (c & 0x0F)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((c & 0xF0) | (a >> 4)); }
                    else if (algo == 18) { pak[pak.Length - 1 - index] = (byte)((a << 4) | (c >> 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a >> 4) | (c << 4)); }
                    else if (algo == 19) { pak[pak.Length - 1 - index] = (byte)((a >> 4) | (a << 4)); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = c; }
                    else if (algo == 20) { pak[pak.Length - 1 - index] = (byte)(((a ^ c) & 0x0F) ^ a); pak[cryptotables_150.offsets[pak.Length - 1 - index]] = (byte)((a << 4) | (c >> 4)); }
                }


                // in the 1.50 protocol, the checksum info is at the trailing end of the pak.
                checksum = (short)pak[pak.Length - 2]; // so get it from there...
                // pak.Length -= 2; // ...and correct the data size
                SoapHexBinary shb = new SoapHexBinary(pak);
                SoapHexBinary shseed = new SoapHexBinary(return_seed);
                string hexa;
                hexa = shb.ToString();
                return (checksum); // finished, pak is decrypted
            }


        }

        static short Pak_ComputeChecksum_150(byte[] pak)
        {
            // this function computes and returns the pak data's checksum
            long index;
            byte sum;
            sum = 0; // start at zero

            // for each byte of data...
            for (index = 0; index < pak.Length; index++)
                sum += pak[index]; // add its inverse value to the checksum
            return (sum); // return the checksum we found
        }

        /// <summary>
        /// IMPLEMENT GLOBAL DECRYPTION LOGIC HERE
        /// </summary>
        /// <param name="packet"></param>
        /// <returns></returns>
        public static byte[] DecryptPacket(byte[] packet)
        {
            //Get info about length ( == 2 <=> ACK PACKET)
            if (packet.Length > 2)
            {
                //Get info about header (Second encryption?)
                byte[] header = Xor(packet.Take(2).Reverse().ToArray());
                byte[] signature = Xor(packet.Skip(2).Take(4).ToArray());

                if (HasSecondCrypto(header,signature))
                {
                    //XOR packet except checksum (1) + seed(4)
                    var checksum = packet.Skip(packet.Length - 5).Take(1).ToArray();
                    var seed = packet.Skip(packet.Length - 4).Take(4).ToArray();
                    var xorData = packet.Take(packet.Length - 5).ToArray();
                    var unXoredData = Xor(xorData);
                    var head = unXoredData.Take(2).ToArray();//.Reverse()
                    //Decrypt150 only pak data 
                    var encrypt150Data = unXoredData.Skip(2).Take(xorData.Length - 2).ToArray();

                    var decrypt150Data = Pak_SwitchEncryptionOff_150(encrypt150Data, seed);

                    //build decrypted packet (decrypt150Data + seed + checksum)
                    return head.Concat(decrypt150Data).Concat(checksum).Concat(seed).ToArray();

                }
                else
                {
                    //XOR packet except checksum (1)
                    var checksum = packet.Skip(packet.Length - 1).Take(1).ToArray();
                    var xorData = packet.Take(packet.Length - 1).ToArray();
                    var unXoredData = Xor(xorData);
                    //build decrypted packet (unXoredData + checksum)
                    return unXoredData.Concat(checksum).ToArray();
                }
            }

            return packet;
        }

        private static bool HasSecondCrypto(byte[] header, byte[] signature)
        {
            bool hasSecondCrypto = false;
            ushort headerShort = ushort.Parse(new SoapHexBinary(header).ToString(), System.Globalization.NumberStyles.HexNumber);
            string binaryString = Convert.ToString(headerShort, 2);
            string sigHexa = new SoapHexBinary(signature).ToString();

            if (binaryString.Length > 15)
            {
                hasSecondCrypto = Convert.ToBoolean(Convert.ToInt32(binaryString[13].ToString())) 
                                  && !sigHexa.Equals("00000000");
            }

            return hasSecondCrypto;
        }
    }
}
